﻿using BuildStatus.Web.Models;
using BuildStatus.Web.Properties;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.ProcessConfiguration.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;

namespace BuildStatus.Web.BuildServer
{
    public class BuildServer : IDisposable, IBuildStatusServer
    {
        internal const string DateTimeFormat = "yyyy.MM.dd HH:mm:ss";

        private TfsTeamProjectCollection _tfsCollection;
        private VersionControlServer _versionControlServer;
        private List<TeamProject> _teamProjects;
        private IBuildServer _buildServer;
        private TeamSettingsConfigurationService _configService;

        internal BuildServer()
        {
            _tfsCollection = GetTeamProjectCollection();
            _versionControlServer = _tfsCollection.GetService<VersionControlServer>();
            _buildServer = (IBuildServer)_tfsCollection.GetService(typeof(IBuildServer));
            _teamProjects = _versionControlServer.GetAllTeamProjects(true).ToList();
            _configService = _tfsCollection.GetService<TeamSettingsConfigurationService>();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _teamProjects.Clear();

                _buildServer = null;
                _versionControlServer = null;

                if (_tfsCollection != null)
                {
                    try
                    {
                        _tfsCollection.Dispose();
                    }
                    catch { }

                    _tfsCollection = null;
                }
            }
        }

        private string GetTeamProjectURL(TeamProject teamProject)
        {
            return teamProject.TeamProjectCollection.Uri.AbsoluteUri.TrimEnd(new char[] { '/', '\\' }) + "/" + teamProject.Name + "/";
        }

        private string GetTaskboardURL(TeamProject teamProject)
        {
            IEnumerable<TeamConfiguration> settings = _configService.GetTeamConfigurationsForUser(new string[] { teamProject.ArtifactUri.AbsoluteUri });
            string currentIterationPath = settings.First().TeamSettings.CurrentIterationPath;

            return GetTeamProjectURL(teamProject) + "_backlogs/TaskBoard/" + currentIterationPath;
        }

        private string GetBuildsURL(TeamProject teamProject)
        {
            return GetTeamProjectURL(teamProject) + "_build";
        }

        public IEnumerable<BuildProject> GetBuildProjects()
        {
            List<BuildProject> result = new List<BuildProject>();

            foreach (TeamProject proj in _teamProjects.OrderBy(x => x.Name))
            {
                BuildProject buildProject = new BuildProject()
                {
                    Name = proj.Name,
                    TaskboardURL = GetTaskboardURL(proj),
                    BuildsURL = GetBuildsURL(proj)
                };

                foreach (IBuildDefinition tfsBuildDefinition in _buildServer.QueryBuildDefinitions(proj.Name))
                {
                    IBuildDetailSpec buildSpec = _buildServer.CreateBuildDetailSpec(proj.Name, tfsBuildDefinition.Name);
                    buildSpec.InformationTypes = new string[] { "CustomSummaryInformation" };
                    buildSpec.MinChangedTime = DateTime.Now.AddDays(-Settings.Default.MaxQueryDays);
                    buildSpec.QueryOrder = BuildQueryOrder.StartTimeDescending;
                    buildSpec.Status = Microsoft.TeamFoundation.Build.Client.BuildStatus.All;

                    IBuildQueryResult builds = _buildServer.QueryBuilds(buildSpec);
                    List<IBuildDetail> buildDetails = builds.Builds.ToList();

                    List<BuildDefinition> buildDefinitions = buildDetails.Where(x => x.BuildController != null).Select(x => GetBuildDefinition(x)).ToList();


                    IQueuedBuildSpec queueSpec = _buildServer.CreateBuildQueueSpec(proj.Name, tfsBuildDefinition.Name);
                    queueSpec.Status = QueueStatus.Queued | QueueStatus.Postponed;
                    List<IQueuedBuild> queuedBuilds = _buildServer.QueryQueuedBuilds(queueSpec).QueuedBuilds.OrderBy(x => x.QueuePosition).ThenBy(x => x.QueueTime).ToList();
                    buildDefinitions.AddRange(queuedBuilds.Select(x => GetBuildDefinition(x)));

                    if (buildDefinitions.Count > 0)
                    {
                        buildDefinitions.Sort(new BuildComparer());

                        BuildDefinition buildDefinition = buildDefinitions.First();
                        buildDefinition.History.AddRange(buildDefinitions.Skip(1).Take(Settings.Default.MaxHistoryBuilds));
                        buildProject.Builds.Add(buildDefinition);
                    }
                    else
                    {
                    }
                }

                if (buildProject.Builds.Count > 0)
                    result.Add(buildProject);
            }

            return result;
        }

        public BuildLog GetBuildLog(string buildNumber)
        {
            BuildLog result = new BuildLog();

            foreach (TeamProject proj in _teamProjects)
            {
                IBuildDetailSpec buildSpec = _buildServer.CreateBuildDetailSpec(proj.Name);
                buildSpec.InformationTypes = null;
                buildSpec.BuildNumber = buildNumber;
                buildSpec.Status = Microsoft.TeamFoundation.Build.Client.BuildStatus.All;

                IBuildDetail buildDetail = _buildServer.QueryBuilds(buildSpec).Builds.FirstOrDefault();
                if (buildDetail != null)
                {
                    result.Status = buildDetail.Status.ToString();

                    buildDetail = _buildServer.GetAllBuildDetails(buildDetail.Uri);
                    List<IBuildStep> buildSteps = InformationNodeConverters.GetBuildSteps(buildDetail);

                    List<IActivityTracking> trackings = InformationNodeConverters.GetActivityTrackingNodes(buildDetail).Where(x => x.Node.Parent == null).ToList();
                    foreach (IActivityTracking tracking in trackings)
                    {
                        BuildLogEntry buildLogEntry = GetBuildStatus(tracking.Node);
                        if (buildLogEntry != null)
                            result.AddChild(buildLogEntry);
                    }
                }
            }

            return result;
        }

        public void RetryBuild(string buildNumber)
        {
            foreach (TeamProject proj in _teamProjects.OrderBy(x => x.Name))
            {
                IBuildDetailSpec buildSpec = _buildServer.CreateBuildDetailSpec(proj.Name);
                buildSpec.InformationTypes = null;
                buildSpec.BuildNumber = buildNumber;
                buildSpec.Status = Microsoft.TeamFoundation.Build.Client.BuildStatus.All;

                IBuildDetail build = _buildServer.QueryBuilds(buildSpec).Builds.FirstOrDefault();
                if (build != null)
                {
                    IQueuedBuild queuedBuild = build.Requests.OrderByDescending(x => x.QueueTime).FirstOrDefault();
                    if (queuedBuild != null)
                    {
                        IQueuedBuild[] newQueued = _buildServer.RetryQueuedBuilds(new IQueuedBuild[] { queuedBuild }, Guid.NewGuid(), QueuedBuildRetryOption.CompletedBuild);
                        break;
                    }
                }
            }
        }

        #region Helpers

        private TfsClientCredentials GetTfsCredentials()
        {
            if (string.IsNullOrEmpty(BuildSettings.Settings.TFSUsername))
                return null;

            NetworkCredential netCred = new NetworkCredential(BuildSettings.Settings.TFSUsername, BuildSettings.Settings.TFSPassword);
            BasicAuthCredential basicCred = new BasicAuthCredential(netCred);

            TfsClientCredentials tfsCred = new TfsClientCredentials(basicCred);
            tfsCred.AllowInteractive = false;

            return tfsCred;
        }

        private TfsTeamProjectCollection GetTeamProjectCollection()
        {
            TfsClientCredentials credentials = GetTfsCredentials();

            Uri tfsUri = new Uri(BuildSettings.Settings.TFSUri);
            TfsTeamProjectCollection result = (credentials == null) ? new TfsTeamProjectCollection(tfsUri) : new TfsTeamProjectCollection(tfsUri, credentials);

            if (credentials != null)
                result.Authenticate();

            return result;
        }

        private static BuildDefinition GetBuildDefinition(IBuildDetail build)
        {
            DateTime finishTime = (build.Status == Microsoft.TeamFoundation.Build.Client.BuildStatus.InProgress) ? DateTime.Now : build.FinishTime;
            IBuildAgent buildAgent = build.BuildController.Agents.FirstOrDefault(x => x.ReservedForBuild == build.Uri);

            List<IBuildInformationNode> versionNodes = build.Information.GetNodesByType("CustomSummaryInformation", true);
            List<KeyValuePair<string, string>> versionsKvp = versionNodes.SelectMany(x => x.Fields).ToList();
            KeyValuePair<string, string> versionKvp = versionsKvp.FirstOrDefault(x => x.Key == "Message" && x.Value.StartsWith("Version: "));

            string buildName = build.BuildDefinition.Name;
            if (versionKvp.Value != null)
            {
                string newVersion = versionKvp.Value.Substring(9).Trim();
                int lastIdx = newVersion.LastIndexOf('.');
                if (lastIdx > 0 && buildName.Contains(newVersion.Substring(0, lastIdx)))
                {
                    string oldVer = newVersion.Substring(0, lastIdx);
                    buildName = buildName.Replace(oldVer, newVersion);
                }
                else
                {
                    buildName += " " + newVersion;
                }
            }

            BuildDefinition buildDefinition = new BuildDefinition
            {
                Name = buildName,
                DisplayClass = build.Status.ToString(),
                Status = build.Status.ToString(),
                RequestedBy = build.RequestedFor,
                QueueTime = build.StartTime.ToString(DateTimeFormat),
                StartTime = build.StartTime.ToString(DateTimeFormat),
                // Duration = (finishTime - build.StartTime).ToString("g"),
                Duration = (finishTime - build.StartTime).ToString(@"hh\:mm\:ss"),
                BuildNumber = build.BuildNumber,
                Version = (versionKvp.Value != null) ? versionKvp.Value.Substring(9) : "",
                IsWaiting = (buildAgent == null && build.Status == Microsoft.TeamFoundation.Build.Client.BuildStatus.InProgress)
            };

            if (buildDefinition.IsWaiting)
            {
                buildDefinition.DisplayClass = "Queued";
                buildDefinition.Status = "Queued";
            }

            return buildDefinition;
        }

        private static BuildDefinition GetBuildDefinition(IQueuedBuild queuedBuild)
        {
            BuildDefinition buildDefinition = new BuildDefinition
            {
                Name = queuedBuild.BuildDefinition.Name,
                DisplayClass = queuedBuild.Status.ToString(),
                Status = queuedBuild.Status.ToString(),
                RequestedBy = queuedBuild.RequestedFor,
                StartTime = queuedBuild.QueueTime.ToString(DateTimeFormat),
                // Duration = (finishTime - build.StartTime).ToString("g"),
                Duration = (DateTime.Now.ToUniversalTime() - queuedBuild.QueueTime.ToUniversalTime()).ToString(@"hh\:mm\:ss"),
                BuildNumber = queuedBuild.Id.ToString(),
                IsWaiting = true
            };

            return buildDefinition;
        }

        private static BuildLogEntry GetBuildStatus(IBuildInformationNode buildInfoNode)
        {
            BuildLogEntry result = null;

            if (buildInfoNode.Type == "ActivityTracking" || buildInfoNode.Type == "AgentScopeActivityTracking")
            {
                DateTime finishTime = DateTime.Parse(buildInfoNode.Fields["FinishTime"]);
                if (finishTime.Year < 1900)
                    finishTime = DateTime.Now;
                TimeSpan duration = finishTime - DateTime.Parse(buildInfoNode.Fields["StartTime"]);

                result = new BuildLogEntry();
                result.Type = "ActivityTracking"; // buildInfoNode.Type;
                result.DisplayText = buildInfoNode.Fields["DisplayText"];
                result.Duration = TimeSpanToStr(duration);
            }

            if (buildInfoNode.Type == "BuildError" || buildInfoNode.Type == "BuildWarning")
            {
                string filename = (buildInfoNode.Fields.ContainsKey("File") ? buildInfoNode.Fields["File"] : "") ?? "";
                if (filename != "")
                {
                    filename = Path.GetFileName(filename);
                    filename += string.Format(" ({0}): ", buildInfoNode.Fields["LineNumber"]);
                }

                result = new BuildLogEntry();
                result.Type = buildInfoNode.Type;
                result.DisplayText = buildInfoNode.Fields["Message"];
                result.Duration = "";
            }

            if (buildInfoNode.Type == "BuildProject")
            {
                result = new BuildLogEntry();
                result.Type = "ActivityTracking"; // buildInfoNode.Type;
                result.DisplayText = string.Format("Built {0} for {1} targets.", buildInfoNode.Fields["ServerPath"], string.IsNullOrEmpty(buildInfoNode.Fields["TargetNames"]) ? "default" : buildInfoNode.Fields["TargetNames"]);
                result.Duration = "";
            }


            if (result != null)
            {
                foreach (IBuildInformationNode childNode in buildInfoNode.Children.Nodes)
                {
                    result.AddChild(GetBuildStatus(childNode));
                }
            }

            return result;
        }

        private static string TimeSpanToStr(TimeSpan self)
        {
            StringBuilder result = new StringBuilder();

            if (self.Days > 0)
            {
                result.Append(self.Days);
                result.Append("days ");
            }

            if (self.Hours > 0 || result.Length > 0)
            {
                result.Append(self.Hours.ToString().PadLeft(2, '0'));
                result.Append(":");
            }

            result.Append(self.Minutes.ToString().PadLeft(2, '0'));
            result.Append(":");

            result.Append(self.Seconds.ToString().PadLeft(2, '0'));

            return result.ToString().Trim();
        }

        #endregion Helpers
    }
}